package com.imon.web.codesphere;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.tmatesoft.svn.core.ISVNLogEntryHandler;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

import com.imon.web.codesphere.data.FileSource;
import com.imon.web.codesphere.data.RepositoryInfo;
import com.imon.web.codesphere.data.RevisionChangeSet;
import com.imon.web.codesphere.data.RevisionInfo;

public class SubversionCodeRepository {
	private static final Log LOG = LogFactory.getLog(SubversionCodeRepository.class);
	private static final String DEFAULT_BINARY_FILE_EXTENSIONS = "gz;war;jar;ear;zip";

	private final RepositoryInfo r;

	private SVNRepository svnRepository;

	private String binaryFileExtensions;

	public SubversionCodeRepository(final RepositoryInfo aRepositoryInfo) {
		r = aRepositoryInfo;
		connect();
	}

	public void setBinaryExtensions(final String fileExtensions) {
		this.binaryFileExtensions = fileExtensions;
	}

	public void connect() {
		LOG.debug(String.format("connect(%s, %s, %.2s, %.2s)", r.getRepositoryTitle(), r.getLocation(),
				r.getUsername(), r.getPassword()));

		if (StringUtils.isEmpty(r.getLocation())) {
			LOG.error("SVN Repository URL is null or empty");
			return;
		}

		try {
			SVNRepositoryFactoryImpl.setup();
			DAVRepositoryFactory.setup();

			final SVNURL svnurl = SVNURL.parseURIDecoded(r.getLocation());
			svnRepository = SVNRepositoryFactory.create(svnurl);
			final ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(r.getUsername(),
					r.getPassword());
			svnRepository.setAuthenticationManager(authManager);

			LOG.debug(String.format("Repository: %s", svnRepository.getLocation()));
			LOG.debug(String.format("Repository UUID: %s", svnRepository.getRepositoryUUID(true)));
			LOG.debug(String.format("Latest Revision: %s", svnRepository.getLatestRevision()));

			updateRepositoryInfo();
		} catch (final SVNException e) {
			LOG.error("Error connecting to url:" + r.getLocation(), e);
		}
	}

	private void updateRepositoryInfo() throws SVNException {
		r.setLocation(svnRepository.getLocation().toString());
		r.setLastRevision(svnRepository.getLatestRevision());
		r.setRepositoryUUID(svnRepository.getRepositoryUUID(true));
	}

	public void close() {
		LOG.debug("close");
		if (svnRepository != null) {
			svnRepository.closeSession();
		}
	}

	public RepositoryInfo getRepositoryInfo() {
		return r;
	}

	public Collection<RevisionInfo> log(final long startRevision, final long endRevision) {
		LOG.debug(String.format("log(%s, %s)", startRevision, endRevision));

		final List<RevisionInfo> logEntries = new ArrayList<RevisionInfo>();

		try {
			svnRepository.log(new String[] { "" }, startRevision, endRevision, true, true, new ISVNLogEntryHandler() {

				@Override
				public void handleLogEntry(final SVNLogEntry logEntry) throws SVNException {
					final RevisionInfo revisionInfo = new RevisionInfo(svnRepository.getLocation().toString(), logEntry);
					logEntries.add(revisionInfo);
				}
			});

		} catch (final SVNException e) {
			LOG.error("Err:", e);
			return Collections.emptyList();
		}

		return logEntries;
	}

	public long getLastRevision() {
		if (svnRepository != null)
			return r.getLastRevision();

		return 0;
	}

	public void processRevisionFiles(final Collection<RevisionInfo> revisionHistory,
			final Closure<FileSource, ?> continuation) {
		LOG.debug(String.format("processRevisionFiles(revisions=%s)", revisionHistory.size()));

		for (final RevisionInfo revisionInfo : revisionHistory) {
			processRevision(continuation, revisionInfo);
		}

	}

	private void processRevision(final Closure<FileSource, ?> continuation, final RevisionInfo revisionInfo) {
		LOG.debug(String.format("Checking revision (%s) for changeSet size: (%s)", revisionInfo.getRevision(),
				revisionInfo.getRevisionChanges().size()));
		for (final RevisionChangeSet changeSet : revisionInfo.getRevisionChanges()) {
			LOG.debug(String.format("Changeset kind: (%s) and type: (%s)", changeSet.getFileKind(),
					changeSet.getChangeType()));
			if (fetchable(changeSet)) {
				saveFileSource(continuation, revisionInfo, changeSet);
			}
		}
	}

	private boolean fetchable(final RevisionChangeSet changeSet) {
		if ((changeSet.getFileKind().equals("file") || changeSet.getFileKind().equals("unknown"))
				&& changeSet.getChangeType() != 'D')
			return true;

		return false;
	}

	private void saveFileSource(final Closure<FileSource, ?> continuation, final RevisionInfo revisionInfo,
			final RevisionChangeSet changeSet) {
		try {
			final ByteArrayOutputStream fileContents = new ByteArrayOutputStream();

			final FileSource fileSource = new FileSource();
			fileSource.setFileUrl(changeSet.getPath());
			fileSource.setRevision(revisionInfo.getRevision());

			if (isBinary(changeSet.getPath())) {
				LOG.debug(String.format("Binary file. Ignoring source code for (%s) ", changeSet.getPath()));
				fileSource.setSourceCode("<binary>");
			} else {
				LOG.debug(String.format("Getting file (%s) for revision (%s)", changeSet.getPath(),
						revisionInfo.getRevision()));
				svnRepository.getFile(changeSet.getPath(), revisionInfo.getRevision(), null, fileContents);
				fileSource.setSourceCode(StringUtils.replace(fileContents.toString(), "\r\n", "\n"));
			}

			continuation.handle(fileSource);

		} catch (final SVNException e) {
			LOG.error(String.format("Error retrieving the file for revision: (%s), changeType: (%c) and path (%s)",
					revisionInfo.getRevision(), changeSet.getChangeType(), changeSet.getPath()), e);
		}
	}

	public boolean isBinary(final String fileUrl) {

		if (StringUtils.isEmpty(binaryFileExtensions)) {
			setBinaryExtensions(DEFAULT_BINARY_FILE_EXTENSIONS);
		}

		final String[] extensions = binaryFileExtensions.split(";");

		for (final String extension : extensions) {
			if (StringUtils.endsWithIgnoreCase(fileUrl, extension)) {
				return true;
			}
		}

		return false;
	}
}
